﻿//-------------------------------------------------------------------------------------------
// <copyright file="JsonRippleProvider.cs" company="Russell Wilkins">
//     Copyright (c) Russell Wilkins 2014. All Rights Reserved.
// </copyright>
// <author>Russell Wilkins</author>
// <license href="license.txt">
//     The use of this software is governed by the Microsoft Public License
//     which is included with this distribution.
// </license>
//-------------------------------------------------------------------------------------------
namespace Ripple.Providers
{
    using System;
    using System.Configuration;
    using System.Diagnostics;
    using System.IO;
    using System.Net;
    using System.Text;
    using System.Web.Script.Serialization;
    using Configuration;
    using System.Collections.Generic;

    /// <summary>
    /// Class to access ripple using json web requests.
    /// </summary>
    public class JsonRippleProvider : IRippleProvider
    {
        private JavaScriptSerializer jsonSerializer = new JavaScriptSerializer();

        /// <summary>
        /// Initializes a new instance of the JsonRippleProvider class using configuration
        /// loaded from the app config file of default if no config found.
        /// </summary>
        public JsonRippleProvider()
        {
            Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
            RippleConfiguration rippledConfig = (RippleConfiguration)config.Sections["ripple"];

            // Use default values if no config.
            if (rippledConfig == null)
            {
                rippledConfig = new RippleConfiguration();
            }

            this.Uri = new Uri(string.Format(@"https://{0}:{1}", rippledConfig.Server.Address, rippledConfig.Server.Port));
        }

        /// <summary>
        /// Initializes a new instance of the JsonRippleProvider class using the uri provided for
        /// the rippled server.
        /// </summary>
        /// <param name="uri">The uri of the rippled server.</param>
        public JsonRippleProvider(string uri)
        {
            this.Uri = new Uri(uri);
        }

        public Uri Uri { get; private set; }

        public AccountInfoResult AccountInfo(string accountId, string ledger_index)
        {
            // Validate parameters
            if (accountId == null)
            {
                throw new ArgumentNullException("accountId");
            }

            if (string.IsNullOrWhiteSpace(accountId))
            {
                throw new ArgumentException("Must pass a value", "accountId");
            }

            // Build request
            Request request = new Request("account_info");
            request.@params.Add(new { account = accountId.Trim(), ledger_index = ledger_index });

            AccountInfoResultHeader header = this.Process<AccountInfoResultHeader>(request);
            return header.Result;
        }

        public AccountLinesResult AccountLines(string accountId, string ledger_index, string peerAccountId = null, object marker = null)
        {
            // Validate parameters
            if (accountId == null)
            {
                throw new ArgumentNullException("accountId");
            }

            if (string.IsNullOrWhiteSpace(accountId))
            {
                throw new ArgumentException("Must pass a value", "accountId");
            }

            // Build request
            Request request = new Request("account_lines");
            Dictionary<string, object> requestParams = new Dictionary<string, object>();
            requestParams.Add("account", accountId.Trim());
            requestParams.Add("ledger_index", ledger_index);
            requestParams.Add("peer", peerAccountId);
            if (marker != null) requestParams.Add("marker", marker);
            request.@params.Add(requestParams);

            AccountLinesResultHeader header = this.Process<AccountLinesResultHeader>(request);
            return header.Result;
        }

        public AccountOffersResult AccountOffers(string accountId, string ledger_index, object marker = null)
        {
            // Validate parameters
            if (accountId == null)
            {
                throw new ArgumentNullException("accountId");
            }

            if (string.IsNullOrWhiteSpace(accountId))
            {
                throw new ArgumentException("Must pass a value", "accountId");
            }

            // Build request
            Request request = new Request("account_offers");
            Dictionary<string, object> requestParams = new Dictionary<string, object>();
            requestParams.Add("account", accountId.Trim());
            requestParams.Add("ledger_index", ledger_index);
            if (marker != null) requestParams.Add("marker", marker);
            request.@params.Add(requestParams);

            AccountOffersResultHeader header = this.Process<AccountOffersResultHeader>(request);
            return header.Result;
        }

        public AccountTransactionsResult AccountTransactions(string accountId, int ledgerIndexMin = -1, int ledgerIndexMax = -1, object marker = null)
        {
            // Validate parameters
            if (accountId == null)
            {
                throw new ArgumentNullException("accountId");
            }

            if (string.IsNullOrWhiteSpace(accountId))
            {
                throw new ArgumentException("Must pass a value", "accountId");
            }

            // Build request
            Request request = new Request("account_tx");
            Dictionary<string, object> requestParams = new Dictionary<string, object>();
            requestParams.Add("account", accountId.Trim());
            requestParams.Add("ledger_index_min", ledgerIndexMin);
            requestParams.Add("ledger_index_max", ledgerIndexMax);
            if (marker != null) requestParams.Add("marker", marker);
            request.@params.Add(requestParams);

            AccountTransactionsResultHeader header = this.Process<AccountTransactionsResultHeader>(request);
            return header.Result;
        }

        /// <summary>
        /// Call the ping method on the rippled server.
        /// </summary>
        /// <returns>Result of the method call.</returns>
        public PingResult Ping()
        {
            PingResultHeader header = this.Process<PingResultHeader>(new Request("ping"));
            return header.Result;
        }

        public RandomResult Random()
        {
            RandomResultHeader header = this.Process<RandomResultHeader>(new Request("random"));
            return header.Result;
        }

        public ServerInfoResult ServerInfo()
        {
            ServerInfoResultHeader header = this.Process<ServerInfoResultHeader>(new Request("server_info"));
            return header.Result;
        }

        public ServerStateResult ServerState()
        {
            ServerStateResultHeader header = this.Process<ServerStateResultHeader>(new Request("server_state"));
            return header.Result;
        }

        private WebRequest CreateWebRequest()
        {
            HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(this.Uri);
            webRequest.ContentType = "application/json-rpc";
            webRequest.Method = "POST";
            return webRequest;
        }

        private T Process<T>(Request request)
            where T : class
        {
            dynamic header = this.jsonSerializer.Deserialize<T>(this.Send(request));

            // TODO: Check for universal errors and throw exception here
            Result result = header.Result;

            Trace.WriteLine("rippled result:");
            Trace.WriteLine("  status.......: " + result.Status);
            Trace.WriteLine("  error........: " + result.Error);
            Trace.WriteLine("  error_code...: " + result.Error_Code);
            Trace.WriteLine("  error_message: " + result.Error_Message);

            return header as T;
        }

        private string Send(Request request)
        {
            string s = this.jsonSerializer.Serialize(request);
            Trace.WriteLine("Request: " + s);

            byte[] bytes = Encoding.UTF8.GetBytes(s);
            WebRequest webRequest = this.CreateWebRequest();
            webRequest.ContentLength = bytes.Length;
            Stream dataStream = webRequest.GetRequestStream();
            dataStream.Write(bytes, 0, bytes.Length);
            dataStream.Close();

            Stream rs = webRequest.GetResponse().GetResponseStream();
            StreamReader rr = new StreamReader(rs, Encoding.UTF8);
            string r = rr.ReadToEnd().TrimEnd();
            Trace.WriteLine("Response: " + r);

            return r;
        }
    }
}